// The Swift Programming Language
// https://docs.swift.org/swift-book
import Foundation

/// A macro that produces both a value and a string containing the
/// source code that generated the value. For example,
///
///     #stringify(x + y)
///
/// produces a tuple `(x + y, "x + y")`.
@freestanding(expression)
public macro stringify<T>(_ value: T) -> (T, String) =
    #externalMacro(module: "HareMacroImpl", type: "StringifyMacro")

//@attached(accessor)
//public macro PropStore<T>(miss: T, store: UserDefaults? = nil) =
//    #externalMacro(module: "HareMacroImpl", type: "PropStore")

//@attached(accessor)
//public macro PropStore(store: UserDefaults? = nil) =
//    #externalMacro(module: "HareMacroImpl", type: "PropStore")

//@attached(accessor)
//public macro PropExt<T>(miss: T, key: String? = nil) =
//    #externalMacro(module: "HareMacroImpl", type: "PropExt")
//@attached(accessor)
//public macro PropExt(key: String? = nil) = #externalMacro(module: "HareMacroImpl", type: "PropExt")

// @attached(accessor)
// public macro PropDelegate<T: PropertyDelegate, V>(target: T, miss: V, key: String? = nil) =
//     #externalMacro(module: "HareMacroImpl", type: "PropDelegate")

//@attached(accessor)
//public macro PropDelegate<T: PropertyDelegate>(target: T, key: String? = nil) =
//    #externalMacro(module: "HareMacroImpl", type: "PropDelegate")

public protocol PropertyDelegate {
    func getProperty(obj: Any?, key: String) -> Any?
    func setProperty(obj: Any?, key: String, value: Any?)
}

private class ExtPropMap: NSObject {
    fileprivate var map: [String: Any] = [:]

}

nonisolated(unsafe) private let _ext_prop_key_: UnsafeMutableRawPointer = malloc(1)

extension NSObject {

    public func getExtProp(key: String) -> Any? {
        return _extProps.map[key]
    }
    public func setExtProp(key: String, value: Any?) {
        if let v = value {
            _extProps.map[key] = v
        } else {
            _extProps.map.removeValue(forKey: key)
        }
    }
    public func removeExtProp(key: String) -> Any? {
        _extProps.map.removeValue(forKey: key)
    }

    fileprivate var _extProps: ExtPropMap {
        if let m = objc_getAssociatedObject(self, _ext_prop_key_) as? ExtPropMap {
            return m
        } else {
            let map = ExtPropMap()
            objc_setAssociatedObject(self, _ext_prop_key_, map, .OBJC_ASSOCIATION_RETAIN)
            return map
        }

    }
}
